
local window =    require "guac.window"
local screen =    require "guac.screen"
local dialog =    require "guac.dialog"
local mouse =     require "guac.mouse"
local scene =     require "guac.scene"
local keyboard =  require "guac.keyboard"
local LOG =       require "guac.log"
local debug =     require "debug"

local tconcat, tremove
    = table.concat, table.remove
local assert, type, tonumber, tostring, ipairs, pairs, print, xpcall, error
    = assert, type, tonumber, tostring, ipairs, pairs, print, xpcall, error


module (...)



local onshutdown_callbacks = {}

function onshutdown(func)
  onshutdown_callbacks[#onshutdown_callbacks+1] = func
end

function shutdown()
  for i,func in ipairs(onshutdown_callbacks) do
    func()
  end
end

function launch(def)

  local screen_details = def.screen_details or {}
  assert(type(screen_details) == 'table', 'screen_details must be a table')
  do
    screen.dimensions(tonumber(screen_details.width) or 320, tonumber(screen_details.height) or 200)
  end
  
  local window_details = def.window_details or {}
  assert(type(window_details) == 'table', 'window_details must be a table')
  do
    local onclose = window_details.onclose
    if onclose then
      window.onclose(onclose)
    end
    window.title(window_details.title or 'Guacamole 1.0')
  end
  
  window.open()
  
  if def.onbegin then
    def.onbegin()
  end
  
  local fps = tonumber(def.fps) or 30
  assert(fps > 0, "fps must be greater than 0")
  
  local _onupdate = def.onupdate
  assert(type(_onupdate) == 'function', 'onupdate() must be defined')
  local function update()
    mouse.refresh()
    keyboard.refresh()
    _onupdate()
    screen.redraw()
  end
  window.addInterval(update, 1000/fps)
  
  screen.declareRenderFunc(function()
    scene.root:render()
  end)
  
  window.loop()
  
  shutdown()
  
end

local dir = ""

function setprojectdir(new_dir)
  dir = new_dir
end

function fatalerrorhandler(msg)
  local errorbuf = {tostring(msg)}
  local level = 2
  local infos = {}
  while true do
    local info = debug.getinfo(level)
    if info then
      infos[#infos+1] = info
    else
      break
    end
    level = level + 1
  end
  if (infos[1].func == error) or (infos[1].name == assert) then
    tremove(infos, 1)
  end
  for i = #infos, 1, -1 do
    local info = infos[i]
    if (info.func == xpcall) then
      for j = i, #infos do
        infos[j] = nil
      end
      break
    end
  end
  for i,info in ipairs(infos) do
    local name, namewhat = info.name or '[anonymous]', info.namewhat
    local short_src = info.short_src
    if (short_src:sub(1, #dir) == dir) then
      short_src = short_src:sub(#dir+1)
    end
    if (namewhat == 'global') or (namewhat == 'method') then
      name = 'function ' .. name .. '()'
    elseif (namewhat == 'local') or (namewhat == 'upvalue') then
      name = 'local function ' .. name .. '()'
    elseif (namewhat == '') then
      name = 'anonymous function'
    end
    if (info.what == "main") then
      errorbuf[#errorbuf+1] = " => " .. short_src .. " [line " .. info.currentline .. "] main body"
    elseif (info.what == "Lua") then
      errorbuf[#errorbuf+1] = " => " .. short_src .. " [line " .. info.currentline .. "] " .. name
    elseif (info.what == "C") then
      errorbuf[#errorbuf+1] = " => " .. name
    elseif (info.what == "tail") then
      errorbuf[#errorbuf+1] = " => [tail call(s)]"
      for k,v in pairs(info) do
        errorbuf[#errorbuf+1] = tostring(k) .. " = " .. tostring(v)
      end
    end
    level = level + 1
  end
  for i,line in ipairs(errorbuf) do
    LOG:fatal(line)
  end
  errorbuf = tconcat(errorbuf, '\n')
  dialog.alert(errorbuf)
  return msg
end

function nonfatalerrorhandler(msg)
  local errorbuf = {tostring(msg)}
  local level = 2
  local infos = {}
  while true do
    local info = debug.getinfo(level)
    if info then
      infos[#infos+1] = info
    else
      break
    end
    level = level + 1
  end
  if (infos[1].func == error) or (infos[1].name == assert) then
    tremove(infos, 1)
  end
  for i = #infos, 1, -1 do
    local info = infos[i]
    if (info.name == "xpcall") then
      for j = i, #infos do
        infos[j] = nil
      end
      break
    end
  end
  for i,info in ipairs(infos) do
    local name, namewhat = info.name or '[anonymous]', info.namewhat
    local short_src = info.short_src
    if (short_src:sub(1, #dir) == dir) then
      short_src = short_src:sub(#dir+1)
    end
    if (namewhat == 'global') or (namewhat == 'method') then
      name = 'function ' .. name .. '()'
    elseif (namewhat == 'local') or (namewhat == 'upvalue') then
      name = 'local function ' .. name .. '()'
    elseif (namewhat == '') then
      name = 'anonymous function'
    end
    if (info.what == "main") then
      errorbuf[#errorbuf+1] = " => " .. short_src .. " [line " .. info.currentline .. "] main body"
    elseif (info.what == "Lua") then
      errorbuf[#errorbuf+1] = " => " .. short_src .. " [line " .. info.currentline .. "] " .. name
    elseif (info.what == "C") then
      errorbuf[#errorbuf+1] = " => " .. name
    elseif (info.what == "tail") then
      errorbuf[#errorbuf+1] = " => [tail call(s)]"
      for k,v in pairs(info) do
        errorbuf[#errorbuf+1] = tostring(k) .. " = " .. tostring(v)
      end
    end
    level = level + 1
  end
  for i,line in ipairs(errorbuf) do
    LOG:warn(line)
  end
  return msg
end
